#pragma once
#include "zMatrixBase.hpp"
#include "zMatrix.hpp"
#include <Math/Vector.hpp>
#include <Math/Matrix.hpp>

namespace zzz{
//////////////////////////////////////////////////////////////////////////
//Vector Dress
template<typename T, unsigned int N,typename Major>
class zMatrixVectorDressR : public zMatrixBaseR<T,Major>
{
public:
  explicit zMatrixVectorDressR(const VectorBase<N,T> &vec)
  :zMatrixBaseR<T,Major>(Major::Dim_==0?1:N,Major::Dim_==0?N:1),vec_(vec){}
  const T operator()(zuint r, zuint c) const {
    if (Major::Dim_==0) {
      ZRCHECK_EQ(r, 0);
      ZRCHECK_LT(c, N);
      return vec_[c];
    } else {
      ZRCHECK_EQ(c, 0);
      ZRCHECK_LT(r, N);
      return vec_[r];
    }
  }

  zSubMatrixR<T,Major> operator()(const int r) const {
    if (Major::Dim_==0)
      return zMatrixBaseR<T,Major>::operator()(0,r);
    else
      return zMatrixBaseR<T,Major>::operator()(r,0);
  }

  zSubMatrixR<T,Major> operator()(const EndlessArray &r) const {
    if (Major::Dim_==0)
      return zMatrixBaseR<T,Major>::operator()(0,r);
    else
      return zMatrixBaseR<T,Major>::operator()(r,0);
  }

  zSubMatrixR<T,Major> operator()(const zMatrixBaseR<int,Major> &r) const {
    if (Major::Dim_==0)
      return zMatrixBaseR<T,Major>::operator()(0,r);
    else
      return zMatrixBaseR<T,Major>::operator()(r,0);
  }
private:
  const VectorBase<N,T> &vec_;
};
template<typename T, unsigned int N,typename Major>
class zMatrixVectorDressW : public zMatrixBaseW<T,Major>
{
public:
  explicit zMatrixVectorDressW(VectorBase<N,T> &vec)
  :zMatrixBaseW<T,Major>(Major::Dim_==0?1:N,Major::Dim_==0?N:1),vec_(vec){}
  const T operator()(zuint r, zuint c) const {
    if (Major::Dim_==0) {
      ZRCHECK_EQ(r, 0);
      ZRCHECK_LT(c, N);
      return vec_[c];
    } else {
      ZRCHECK_EQ(c, 0);
      ZRCHECK_LT(r, N);
      return vec_[r];
    }
  }

  T &operator()(zuint r, zuint c) {
    if (Major::Dim_==0) {
      ZRCHECK_EQ(r, 0);
      ZRCHECK_LT(c, N);
      return vec_[c];
    } else {
      ZRCHECK_EQ(c, 0);
      ZRCHECK_LT(r, N);
      return vec_[r];
    }
  }

  //don't know why this does not work
//  using zMatrixBaseW<T,Major>::operator=;
  const zMatrixBaseW<T,Major>& operator=(const zMatrixBaseR<T,Major> &other) {
    return zMatrixBaseW<T,Major>::operator=(other);
  }

  zSubMatrixW<T,Major> operator()(const int r) {
    if (Major::Dim_==0)
      return zMatrix<T,Major>::operator()(0,r);
    else
      return zMatrix<T,Major>::operator()(r,0);
  }

  zSubMatrixW<T,Major> operator()(const EndlessArray &r) {
    if (Major::Dim_==0)
      return zMatrix<T,Major>::operator()(0,r);
    else
      return zMatrix<T,Major>::operator()(r,0);
  }

  zSubMatrixW<T,Major> operator()(const zMatrixBaseR<int,Major> &r) {
    if (Major::Dim_==0)
      return zMatrix<T,Major>::operator()(0,r);
    else
      return zMatrix<T,Major>::operator()(r,0);
  }
private:
  VectorBase<N,T> &vec_;
};
template<typename T, unsigned int N>
const zMatrixVectorDressR<T,N,zColMajor> Dress(const VectorBase<N,T> &v)
{return zMatrixVectorDressR<T,N,zColMajor>(v);}
template<typename T, unsigned int N>
zMatrixVectorDressW<T,N,zColMajor> Dress(VectorBase<N,T> &v)
{return zMatrixVectorDressW<T,N,zColMajor>(v);}

//////////////////////////////////////////////////////////////////////////
//Matrix Dress
template<typename T, unsigned int R, unsigned int C, class Major>
class zMatrixMatrixDressR : public zMatrixBaseR<T,Major>
{
public:
  explicit zMatrixMatrixDressR(const MatrixBase<R,C,T> &mat):zMatrixBaseR<T,Major>(R,C),mat_(mat){}
  const T operator()(zuint r, zuint c) const {
    CheckRange(r, c);
    return mat_.At(r,c);
  }
  using zMatrixBaseR<T,Major>::operator();
private:
  const MatrixBase<R,C,T> &mat_;
};
template<typename T, unsigned int R, unsigned int C, class Major>
class zMatrixMatrixDressW : public zMatrixBaseW<T,Major>
{
public:
  explicit zMatrixMatrixDressW(MatrixBase<R,C,T> &mat):zMatrixBaseW<T,Major>(R,C),mat_(mat){}
  const T operator()(zuint r, zuint c) const {
    CheckRange(r, c);
    return mat_.At(r,c);
  }

  T &operator()(zuint r, zuint c) {
    CheckRange(r, c);
    return mat_.At(r,c);
  }

  //don't know why this does not work
//  using zMatrixBaseW<T,Major>::operator =;
  const zMatrixBaseW<T,Major>& operator=(const zMatrixBaseR<T,Major> &other) {
    return zMatrixBaseW<T,Major>::operator =(other);
  }
  using zMatrixBaseW<T,Major>::operator();
private:
  MatrixBase<R,C,T> &mat_;
};
template<typename T, unsigned int R, unsigned int C>
const zMatrixMatrixDressR<T,R,C,zColMajor> Dress(const MatrixBase<R,C,T> &v)
{return zMatrixMatrixDressR<T,R,C,zColMajor>(v);}
template<typename T, unsigned int R, unsigned int C>
zMatrixMatrixDressW<T,R,C,zColMajor> Dress(MatrixBase<R,C,T> &v)
{return zMatrixMatrixDressW<T,R,C,zColMajor>(v);}

////////////////////////////////////////////////////
// ArrayBase Dress
template<typename T,class Major>
class zMatrixArrayDressR : public zMatrixBaseR<T,Major>
{
  using zMatrixBaseR<T,Major>::rows_;
  using zMatrixBaseR<T,Major>::cols_;
public:
  explicit zMatrixArrayDressR(const ArrayBase<2,T> &arr):zMatrixBaseR<T,Major>(arr.Size(0),arr.Size(1)),arr_(arr){}
  const T operator()(zuint r, zuint c) const {
    CheckRange(r, c);
    return arr_.At(Vector2ui(r,c));
  }
  using zMatrixBaseR<T,Major>::operator();
private:
  const ArrayBase<2,T> &arr_;
};
template<typename T,class Major>
class zMatrixArrayDressW : public zMatrixBaseW<T,Major>
{
  using zMatrixBaseW<T,Major>::rows_;
  using zMatrixBaseW<T,Major>::cols_;
public:
  explicit zMatrixArrayDressW(ArrayBase<2,T> &arr):zMatrixBaseW<T,Major>(arr.Size(0),arr.Size(1)),arr_(arr){}
  const T operator()(zuint r, zuint c) const
  {
    CheckRange(r, c);
    return arr_.At(Vector2ui(r,c));
  }
  T &operator()(zuint r, zuint c)
  {
    CheckRange(r, c);
    return arr_.At(Vector2ui(r,c));
  }
  const zMatrixBaseW<T,Major> &operator=(const zMatrixBaseR<T,Major> &other)
  {
    arr_.SetSize(Vector2ui(other.rows_,other.cols_));
    rows_=other.rows_;
    cols_=other.cols_;
    return zMatrixBaseW<T,Major>::operator=(other);
  }
  using zMatrixBaseW<T,Major>::operator();
private:
  ArrayBase<2,T> &arr_;
};
template<typename T>
const zMatrixArrayDressR<T,zColMajor> Dress(const ArrayBase<2,T> &v)
{return zMatrixArrayDressR<T,zColMajor>(v);}
template<typename T>
zMatrixArrayDressW<T,zColMajor> Dress(ArrayBase<2,T> &v)
{return zMatrixArrayDressW<T,zColMajor>(v);}
////////////////////////////////////////////////////
// Raw C++ Array Dress
template<typename T,class Major>
class zMatrixRawArrayDressR : public zMatrixBaseR<T,Major>
{
  using zMatrixBaseR<T,Major>::rows_;
  using zMatrixBaseR<T,Major>::cols_;
public:
  explicit zMatrixRawArrayDressR(const T *arr, zuint nrow, zuint ncol):zMatrixBaseR<T,Major>(nrow,ncol),arr_(arr){
    ZCHECK_NOT_NULL(arr);
  }
  const T operator()(zuint r, zuint c) const
  {
    CheckRange(r, c);
    return arr_[Major::ToIndex(r,c,rows_,cols_)];
  }
  using zMatrixBaseR<T,Major>::operator();
private:
  const T *arr_;
};
template<typename T,class Major>
class zMatrixRawArrayDressW : public zMatrixBaseW<T,Major>
{
  using zMatrixBaseW<T,Major>::rows_;
  using zMatrixBaseW<T,Major>::cols_;
public:
  explicit zMatrixRawArrayDressW(T *arr, zuint nrow, zuint ncol):zMatrixBaseW<T,Major>(nrow,ncol),arr_(arr){
    ZCHECK_NOT_NULL(arr);
  }
  const T operator()(zuint r, zuint c) const
  {
    CheckRange(r, c);
    return arr_[Major::ToIndex(r,c,rows_,cols_)];
  }
  T &operator()(zuint r, zuint c)
  {
    CheckRange(r, c);
    return arr_[Major::ToIndex(r,c,rows_,cols_)];
  }
  const zMatrixBaseW<T,Major> &operator=(const zMatrixBaseR<T,Major> &other)
  {
    ZRCHECK_EQ(other.rows_, rows_);
    ZRCHECK_EQ(other.cols_, cols_);
    return zMatrixBaseW<T,Major>::operator=(other);
  }
  using zMatrixBaseW<T,Major>::operator();
private:
  T *arr_;
};
template<typename T>
const zMatrixRawArrayDressR<T,zColMajor> Dress(const T *v, zuint nrow, zuint ncol)
{return zMatrixRawArrayDressR<T,zColMajor>(v,nrow,ncol);}
template<typename T>
zMatrixRawArrayDressW<T,zColMajor> Dress(T *v, zuint nrow, zuint ncol)
{return zMatrixRawArrayDressW<T,zColMajor>(v,nrow,ncol);}
////////////////////////////////////////////
//Dress for single value
template<typename T,class Major>
class zMatrixSingleDressR : public zMatrixBaseR<T,Major>
{
public:
  explicit zMatrixSingleDressR(const T &v):zMatrixBaseR<T,Major>(1,1),v_(v){}
  const T operator()(zuint r, zuint c) const
  {
    CheckRange(r, c);
    return v_;
  }
  using zMatrixBaseR<T,Major>::operator();
private:
  const T &v_;
};
template<typename T,class Major>
class zMatrixSingleDressW : public zMatrixBaseW<T,Major>
{
public:
  explicit zMatrixSingleDressW(T &v):zMatrixBaseW<T,Major>(1,1),v_(v){}
  const T operator()(zuint r, zuint c) const
  {
    CheckRange(r, c);
    return v_;
  }
  T &operator()(zuint r, zuint c)
  {
    CheckRange(r, c);
    return v_;
  }

  // don't know why this does not work
//  using zMatrixBaseW<T,Major>::operator=;
  const zMatrixBaseW<T,Major>& operator=(const zMatrixBaseR<T,Major> &other)
  {
    return zMatrixBaseW<T,Major>::operator =(other);
  }
  using zMatrixBaseW<T,Major>::operator();
private:
  T &v_;
};
//////////////////////////////////////////////////////////////////////////
// std::vector Dress
template<typename T, typename Major>
class zMatrixStdVectorDressR : public zMatrixBaseR<T,Major>
{
public:
  explicit zMatrixStdVectorDressR(const std::vector<T> &vec)
    :zMatrixBaseR<T,Major>(Major::Dim_==0?1:vec.size(),Major::Dim_==0?vec.size():1),vec_(vec){}
  const T operator()(zuint r, zuint c) const {
    if (Major::Dim_==0) {
      ZRCHECK_EQ(r, 0);
      ZRCHECK_LT(c, vec_.size());
      return vec_[c];
    } else {
      ZRCHECK_EQ(c, 0);
      ZRCHECK_LT(r, vec_.size());
      return vec_[r];
    }
  }

  zSubMatrixR<T,Major> operator()(const int r) const {
    if (Major::Dim_==0)
      return zMatrixBaseR<T,Major>::operator()(0,r);
    else
      return zMatrixBaseR<T,Major>::operator()(r,0);
  }

  zSubMatrixR<T,Major> operator()(const EndlessArray &r) const {
    if (Major::Dim_==0)
      return zMatrixBaseR<T,Major>::operator()(0,r);
    else
      return zMatrixBaseR<T,Major>::operator()(r,0);
  }

  zSubMatrixR<T,Major> operator()(const zMatrixBaseR<int,Major> &r) const {
    if (Major::Dim_==0)
      return zMatrixBaseR<T,Major>::operator()(0,r);
    else
      return zMatrixBaseR<T,Major>::operator()(r,0);
  }
private:
  const std::vector<T> &vec_;
};
template<typename T, typename Major>
class zMatrixStdVectorDressW : public zMatrixBaseW<T,Major>
{
public:
  explicit zMatrixStdVectorDressW(std::vector<T> &vec)
    :zMatrixBaseW<T,Major>(Major::Dim_==0?1:vec.size(),Major::Dim_==0?vec.size():1),vec_(vec){}
  const T operator()(zuint r, zuint c) const {
    if (Major::Dim_==0) {
      ZRCHECK_EQ(r, 0);
      ZRCHECK_LT(c, vec_.size());
      return vec_[c];
    } else {
      ZRCHECK_EQ(c, 0);
      ZRCHECK_LT(r, vec_.size());
      return vec_[r];
    }
  }

  T &operator()(zuint r, zuint c) {
    if (Major::Dim_==0) {
      ZRCHECK_EQ(r, 0);
      ZRCHECK_LT(c, vec_.size());
      return vec_[c];
    } else {
      ZRCHECK_EQ(c, 0);
      ZRCHECK_LT(r, vec_.size());
      return vec_[r];
    }
  }

  //don't know why this does not work
  //  using zMatrixBaseW<T,Major>::operator=;
  const zMatrixBaseW<T,Major>& operator=(const zMatrixBaseR<T,Major> &other) {
    if (Major::Dim_==0) {
      ZRCHECK_EQ(other.rows_, 1);
      vec_.resize(other.cols_);
      cols_=other.cols_;
    } else {
      ZRCHECK_EQ(other.cols_, 1);
      vec_.resize(other.rows_);
      rows_=other.rows_;
    }
    return zMatrixBaseW<T,Major>::operator=(other);
  }

  zSubMatrixW<T,Major> operator()(const int r) {
    if (Major::Dim_==0)
      return zMatrix<T,Major>::operator()(0,r);
    else
      return zMatrix<T,Major>::operator()(r,0);
  }

  zSubMatrixW<T,Major> operator()(const EndlessArray &r) {
    if (Major::Dim_==0)
      return zMatrix<T,Major>::operator()(0,r);
    else
      return zMatrix<T,Major>::operator()(r,0);
  }

  zSubMatrixW<T,Major> operator()(const zMatrixBaseR<int,Major> &r) {
    if (Major::Dim_==0)
      return zMatrix<T,Major>::operator()(0,r);
    else
      return zMatrix<T,Major>::operator()(r,0);
  }
private:
  std::vector<T> &vec_;
};
template<typename T>
const zMatrixStdVectorDressR<T,zColMajor> Dress(const std::vector<T> &v)
{return zMatrixStdVectorDressR<T,zColMajor>(v);}
template<typename T>
zMatrixStdVectorDressW<T,zColMajor> Dress(std::vector<T> &v)
{return zMatrixStdVectorDressW<T,zColMajor>(v);}


//ATTENTION: cannot make a template Dress for single value
//since every object can be interpreted this way
#define SINGLEDRESS(T) \
inline const zMatrixSingleDressR<T,zColMajor> Dress(const T &v)\
{return zMatrixSingleDressR<T,zColMajor>(v);}\
inline zMatrixSingleDressW<T,zColMajor> Dress(T &v)\
{return zMatrixSingleDressW<T,zColMajor>(v);}

SINGLEDRESS(zint8);
SINGLEDRESS(zuint8);
SINGLEDRESS(zint16);
SINGLEDRESS(zuint16);
SINGLEDRESS(zint32);
SINGLEDRESS(zuint32);
SINGLEDRESS(float);
SINGLEDRESS(double);
}
